<?
	class textElement
	{
		var $type = 'text';
		var $parent= null;
		var $text;
		function textElement($cTexto)
		{
			$this->text = $cTexto;
		}
		function getText( $cSeparador = "\t", $cFin = "\n" )
		{
			return $this->text ;
		}
		function getTagName()
		{
			return 'text';
		}
	}

	class nodeElement
	{
		var $type = 'node';
		var $parent= null;
		var $tag = "";
		var $properties = array();
		var $elements = array();
		var $tags = array();
		var $nqElements = 0;
		var $positionFrom = 0;
		var $positionTo= 0;

		function getTagName()
		{
			return $this->tag;
		}
		function count()
		{
			return $this->nqElements;
		}
		function getTree( $nqNivel = 0)
		{
			$cReturn = $this->tag . ' (' . $this->nqElements . ')'; 
			/*
			foreach( $this->properties as $name => $value )
			{
				$cReturn .= " $name => $value ";
			}
			*/
			$cReturn .= "<br/>";
			foreach( $this->elements as $element )
			{
				if ( $element->type == 'node' ) 
				{
					for( $nIndex = 0; $nIndex < $nqNivel; $nIndex++ ) $cReturn  .=  "&nbsp;&nbsp;&nbsp;";
					$cReturn  .=  $element->getTree( $nqNivel + 1 ) ;
				}
			}			
			return $cReturn  ;
		}
		function getXML()
		{
			$cReturn = "<" . $this->tag . " ";
			foreach( $this->properties as $name => $value )
			{
				$cReturn .= $name . '="' . $value . '" ';
			}
			foreach( $this->elements as $element )
			{
				if ( $element->type == 'node' ) $cReturn  .=  $element->getXML();
			}
			$cReturn  .= "</" . $this->tag . ">";
			
			return $cReturn  ;
		}
		function getText( $cSeparador = "\t", $cFin = "\n" )
		{
			$cReturn  = "";
			foreach( $this->elements as $element )
			{

				if ( $element->type  == 'node'  ) $cReturn  .=  $element->getText( $cSeparador , $cFin )  .  $cSeparador;
				elseif  ($element->type  == 'text') $cReturn  .= $element->getText( $cSeparador , $cFin ) . $cSeparador;
			}
			return $cReturn  . $cFin;
		}
		function addProperty( $cPropiedad, $cValor )
		{
			$this->properties[$cPropiedad] = $cValor;
		}
		function &addElement( $oElement )
		{
			// Le enlaza  el parent al child con su puntero
			$oElement->parent =& $this;
			// Agrega el elemento a la lista de elementos
			$this->elements[$this->nqElements] = $oElement;
			// si es un nodo entonces crea un link en la lista de tags
			if ( $oElement->type == 'node'  && $oElement->tag ) $this->tags[$oElement->tag][] =& $oElement;
			//retorna la referencia al objeto en la estrutura
			return $this->elements[$this->nqElements++] ;
		}

		function nodeElement( $tag )
		{
			$this->tag = $tag ;
		}
	}

	function parseHtml ( $cHtml , $cPath = "")
	{
		$oRoot  = new nodeElement("document");
		$nIndex = $nLoop = $nEstado = 0;
		$oActual =& $oRoot;
		$nLen = strlen($cHtml) ;
		$aTagsSinElementos = array( "br", "hr", "img" );
		
		while ( $nIndex < $nLen )
		{
			if ( $nLoop++ > $nLen ) exit("Entro en loop !");
			switch( $nEstado )
			{
				default:
				case 0: // Inicio
					// Saltea espacios
					while ( $nIndex < $nLen && ( $cHtml[$nIndex] == ' '  || $cHtml[$nIndex] == "\t" || $cHtml[$nIndex] == "\r" || $cHtml[$nIndex] == "\n") ) $nIndex++;
					// Si es metaelemento
					if ( $cHtml[$nIndex] == '<' && $cHtml[$nIndex+1] == '?' )
					{
						$nIndex+= 2; // Saltea un caracter para que quede en el ppio del metaelemento
						$nEstado = 6;
						break;
					}
					// Si es un Fin de Tag
					if ( $cHtml[$nIndex] == '<' && $cHtml[$nIndex+1] == '/' )
					{
						$nIndex+= 2; // Saltea un caracter para que quede en el ppio del tag
						$nEstado = 4;
						break;
					}
					// Si es un PPio de Tag
					if ( $cHtml[$nIndex] == '<' && $cHtml[$nIndex+1] != '/' )
					{
						$nIndex++;
						$nEstado = 1;
						break;
					}
					$nEstado = 5;
					break;
				case 1: // Inicio Tag
					// Arma el Nombre del Tag
					$nPositionFrom = $nIndex -1;
					for( $cTag = '' ; $nIndex < $nLen && ($cHtml[$nIndex] != '>'  && $cHtml[$nIndex] != '/'  && $cHtml[$nIndex] != ' '  && $cHtml[$nIndex] != "\t" && $cHtml[$nIndex] != "\r" && $cHtml[$nIndex] != "\n" ); ++$nIndex)
						$cTag .= $cHtml[$nIndex];
					$cTag  = strtolower($cTag );
					// Si es un comentario => lo saltea
					if ( $cTag == '!--' ||  $cTag == '!doctype'  )
					{
						if ( $cTag == '!--'  )
							while ($nIndex < $nLen && ( $cHtml[$nIndex] != '-'  && $cHtml[$nIndex+1] != "-" || $cHtml[$nIndex+2] != ">" ) ) $nIndex++;
						else
							while ($nIndex < $nLen && ( $cHtml[$nIndex] != '>'  ) ) $nIndex++;
						$nEstado = 0;
						break;
					}
					$nEstado = 2;
					$oActual =& $oActual->addElement( new nodeElement( $cTag ) );
					$oActual->positionFrom = $nPositionFrom ;
					//break;
				case 2: // En Tag
					// Saltea espacios
					while ($nIndex < $nLen && ( $cHtml[$nIndex] == ' '  || $cHtml[$nIndex] == "\t" || $cHtml[$nIndex] == "\r" || $cHtml[$nIndex] == "\n") ) $nIndex++;
					// Si es un Tag que se autocierra
					if ( $cHtml[$nIndex] == '/'  ) 
					{
						// Sube un nivel
						$oActual =& $oActual->parent;
						// Saltea  todo hasta el >
						while ($nIndex < $nLen && ( $cHtml[$nIndex] != '>' ) ) $nIndex++;
						// Saltea el > y vuelve a 0
						$nIndex++;
						$nEstado = 0;
						break;
					}
					// in_array( $cTag , $aTagsSinElementos, false )
					if ( $cHtml[$nIndex] == '>' )
					{
						// Si es un Tag que por definicion no tiene elementos
						if ( in_array( $cTag , $aTagsSinElementos )  ) 
						{
							// Sube un nivel
							$oActual =& $oActual->parent;
						}
						// Saltea el > y vuelve a 0
						$nIndex++;
						$nEstado = 0;
						break;
					}
					$nEstado = 3;
					//break;
				case 3: // Inicio Propiedad
					// Arma el Nombre de la Propiedad
					for( $cPropiedad = '' ; $nIndex < $nLen && ($cHtml[$nIndex] != '>'  && $cHtml[$nIndex] != '='  && $cHtml[$nIndex] != ' '  && $cHtml[$nIndex] != "\t" && $cHtml[$nIndex] != "\r" && $cHtml[$nIndex] != "\n" ); ++$nIndex)
						$cPropiedad .= $cHtml[$nIndex];
					// Saltea espacios
					while ($nIndex < $nLen && ( $cHtml[$nIndex] == ' '  || $cHtml[$nIndex] == "\t" || $cHtml[$nIndex] == "\r" || $cHtml[$nIndex] == "\n") ) $nIndex++;
					// Si no viene un = despues del nombre => es una propiedad sin valor (por ej nowrap)
					if ( $cHtml[$nIndex] != '=' )
					{
						$nEstado = 2;
					} else
					{
						// Saltea el =
							$nIndex++;
						// Saltea espacios
							while ($nIndex < $nLen && ( $cHtml[$nIndex] == ' '  || $cHtml[$nIndex] == "\t" || $cHtml[$nIndex] == "\r" || $cHtml[$nIndex] == "\n") ) $nIndex++;
						// Si tiene delimitador => busca hasta el proximo delimitaro
						if ( $cHtml[$nIndex] == '"' || $cHtml[$nIndex] == "'"  ) 
						{
							$cDelimitador = $cHtml[$nIndex] ;
							// Saltea el delimitador
								$nIndex++;
							// Arma el Valor de la Propiedad
								for( $cValor = '' ; $nIndex < $nLen && ($cHtml[$nIndex] != $cDelimitador ); ++$nIndex)
									$cValor  .= $cHtml[$nIndex];
							// Saltea el delimitador
								$nIndex++;
						} else
						{
							// Arma el Valor de la Propiedad
								for( $cValor = '' ; $nIndex < $nLen && ($cHtml[$nIndex] != ">"  && $cHtml[$nIndex] != ' '  && $cHtml[$nIndex] != "\t" && $cHtml[$nIndex] != "\r" && $cHtml[$nIndex] != "\n" ); ++$nIndex)
								{
									$cValor  .= $cHtml[$nIndex];
									//echo $cHtml[$nIndex] . "_" . ord($cHtml[$nIndex]) . "_" ;
								}
						}
					}
					//echo $cPropiedad ."=" .$cValor ."<br>";
					// Agrega la propiedad al elemento actual
					$oActual->addProperty($cPropiedad, $cValor );
					$nEstado = 2;
					break;
				case 4: // Fin Tag
					// Arma el Nombre del Tag
					for( $cTag = '' ; $nIndex < $nLen && ($cHtml[$nIndex] != '>'  && $cHtml[$nIndex] != '/'  && $cHtml[$nIndex] != ' '  && $cHtml[$nIndex] != "\t" && $cHtml[$nIndex] != "\r" && $cHtml[$nIndex] != "\n" ); ++$nIndex)
						$cTag .= $cHtml[$nIndex];
					$cTag  = strtolower($cTag );
					// Saltea  todo hasta el >
					while ($nIndex < $nLen && ( $cHtml[$nIndex] != '>' ) ) $nIndex++;
					// Saltea el >
					$nIndex ++;
					// Si el fin de tag era del actual => sube, sino lo salteo sin hacer nada.
					if ( $cTag == $oActual->tag )
					{
						$oActual->positionTo = $nIndex;
						$oActual =& $oActual->parent;
					}
					$nEstado = 0;
					break;
				case 5: // Texto
					// Arma el Nombre de la Propiedad
					for( $cTexto= '' ; $nIndex < $nLen && ($cHtml[$nIndex] != '<' ); ++$nIndex)
						$cTexto .= $cHtml[$nIndex];
					$oActual->addElement( new textElement( $cTexto) );
					$nEstado = 0;
					break;
				case 6: // Inicio Metalemento
					while ($nIndex < $nLen && ( $cHtml[$nIndex] != '>'  && $cHtml[$nIndex-1] != '?' ) ) $nIndex++;
					$nEstado = 0;
					break;
			}
		}
		return $oRoot ;
	}
?>